package com.raptor.common.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;

import com.raptor.common.annotation.Adaptive;
import com.raptor.common.annotation.SPI;

//@Slf4j
public class ExtensionLoader<T> {

	private static final String rpcx_DIRECTORY = "raptor/";

	private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

	private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

	private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

	private final Class<?> type;

	private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

	private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

	private volatile Class<?> cachedAdaptiveClass = null;

	private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

	private String cachedDefaultName;

	private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

	private static <T> boolean withExtensionAnnotation(Class<T> type) {
		return type.isAnnotationPresent(SPI.class);
	}

	@SuppressWarnings("unchecked")
	public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
		if (type == null)
			throw new IllegalArgumentException("Extension type == null");
		if (!type.isInterface()) {
			throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
		}
		if (!withExtensionAnnotation(type)) {
			throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
		}
		ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
		if (loader == null) {
			EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
			loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
		}
		return loader;
	}

	private ExtensionLoader(Class<?> type) {
		this.type = type;
	}

	public String getExtensionName(T extensionInstance) {
		return getExtensionName(extensionInstance.getClass());
	}

	public String getExtensionName(Class<?> extensionClass) {
		return cachedNames.get(extensionClass);
	}

	@SuppressWarnings("unchecked")
	public T getLoadedExtension(String name) {
		if (name == null || name.length() == 0)
			throw new IllegalArgumentException("Extension name == null");
		Holder<Object> holder = cachedInstances.get(name);
		if (holder == null) {
			cachedInstances.putIfAbsent(name, new Holder<Object>());
			holder = cachedInstances.get(name);
		}
		return (T) holder.get();
	}

	public Set<String> getLoadedExtensions() {
		return Collections.unmodifiableSet(new TreeSet<String>(cachedInstances.keySet()));
	}

	@SuppressWarnings("unchecked")
	public T getExtension(String name) {
		if (name == null || name.length() == 0)
			throw new IllegalArgumentException("Extension name == null");
		if ("true".equals(name)) {
			return getDefaultExtension();
		}
		Holder<Object> holder = cachedInstances.get(name);
		if (holder == null) {
			cachedInstances.putIfAbsent(name, new Holder<Object>());
			holder = cachedInstances.get(name);
		}
		Object instance = holder.get();
		if (instance == null) {
			synchronized (holder) {
				instance = holder.get();
				if (instance == null) {
					instance = createExtension(name);
					holder.set(instance);
				}
			}
		}
		return (T) instance;
	}

	@SuppressWarnings("unchecked")
	private T createExtension(String name) {
		Class<?> clazz = getExtensionClasses().get(name);
		if (clazz == null) {
			throw findException(name);
		}
		try {
			T instance = (T) EXTENSION_INSTANCES.get(clazz);
			if (instance == null) {
				EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
				instance = (T) EXTENSION_INSTANCES.get(clazz);
			}
			return instance;
		} catch (Throwable t) {
			throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ")  could not be instantiated: " + t.getMessage(), t);
		}
	}

	public T getDefaultExtension() {
		Map<String, Class<?>> extensionClasses = getExtensionClasses();
		if (null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) {
			return null;
		}
		return getExtension(cachedDefaultName);
	}

	public boolean hasExtension(String name) {
		if (name == null || name.length() == 0)
			throw new IllegalArgumentException("Extension name == null");
		try {
			return getExtensionClass(name) != null;
		} catch (Throwable t) {
			return false;
		}
	}

	public Set<String> getSupportedExtensions() {
		Map<String, Class<?>> clazzes = getExtensionClasses();
		return Collections.unmodifiableSet(new TreeSet<String>(clazzes.keySet()));
	}

	public String getDefaultExtensionName() {
		getExtensionClasses();
		return cachedDefaultName;
	}

	public void addExtension(String name, Class<?> clazz) {
		getExtensionClasses(); // load classes
		if (!type.isAssignableFrom(clazz)) {
			throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type);
		}
		if (clazz.isInterface()) {
			throw new IllegalStateException("Input type " + clazz + "can not be interface!");
		}
		if (!clazz.isAnnotationPresent(Adaptive.class)) {
			if (StringUtils.isBlank(name)) {
				throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
			}
			if (cachedClasses.get().containsKey(name)) {
				throw new IllegalStateException("Extension name " + name + " already existed(Extension " + type + ")!");
			}

			cachedNames.put(clazz, name);
			cachedClasses.get().put(name, clazz);
		} else {
			if (cachedAdaptiveClass != null) {
				throw new IllegalStateException("Adaptive Extension already existed(Extension " + type + ")!");
			}
			cachedAdaptiveClass = clazz;
		}
	}

	private IllegalStateException findException(String name) {
		for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
			if (entry.getKey().toLowerCase().contains(name.toLowerCase())) {
				return entry.getValue();
			}
		}
		StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name);
		int i = 1;
		for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
			if (i == 1) {
				buf.append(", possible causes: ");
			}
			buf.append("\r\n(");
			buf.append(i++);
			buf.append(") ");
			buf.append(entry.getKey());
			buf.append(":\r\n");
			buf.append(StringUtils.toString(entry.getValue()));
		}
		return new IllegalStateException(buf.toString());
	}

	private Class<?> getExtensionClass(String name) {
		if (type == null)
			throw new IllegalArgumentException("Extension type == null");
		if (name == null)
			throw new IllegalArgumentException("Extension name == null");
		Class<?> clazz = getExtensionClasses().get(name);
		if (clazz == null)
			throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
		return clazz;
	}

	private Map<String, Class<?>> getExtensionClasses() {
		Map<String, Class<?>> classes = cachedClasses.get();
		if (classes == null) {
			synchronized (cachedClasses) {
				classes = cachedClasses.get();
				if (classes == null) {
					classes = loadExtensionClasses();
					cachedClasses.set(classes);
				}
			}
		}
		return classes;
	}

	private Map<String, Class<?>> loadExtensionClasses() {
		final SPI defaultAnnotation = type.getAnnotation(SPI.class);
		if (defaultAnnotation != null) {
			String value = defaultAnnotation.value();
			if (value != null && (value = value.trim()).length() > 0) {
				String[] names = NAME_SEPARATOR.split(value);
				if (names.length > 1) {
					throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
				}
				if (names.length == 1)
					cachedDefaultName = names[0];
			}
		}
		Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
		loadFile(extensionClasses, rpcx_DIRECTORY);
		return extensionClasses;
	}

	private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
		String fileName = dir + type.getName();
		try {
			Enumeration<java.net.URL> urls;
			ClassLoader classLoader = findClassLoader();
			if (classLoader != null) {
				urls = classLoader.getResources(fileName);
			} else {
				urls = ClassLoader.getSystemResources(fileName);
			}
			if (urls != null) {
				while (urls.hasMoreElements()) {
					java.net.URL url = urls.nextElement();
					try {
						BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
						try {
							String line = null;
							while ((line = reader.readLine()) != null) {
								final int ci = line.indexOf('#');
								if (ci >= 0)
									line = line.substring(0, ci);
								line = line.trim();
								if (line.length() > 0) {
									try {
										String name = null;
										int i = line.indexOf('=');
										if (i > 0) {
											name = line.substring(0, i).trim();
											line = line.substring(i + 1).trim();
										}
										if (line.length() > 0) {
											Class<?> clazz = Class.forName(line, true, classLoader);
											if (!type.isAssignableFrom(clazz)) {
												throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
											}
											cachedAdaptiveClass = clazz;
											extensionClasses.put(name, cachedAdaptiveClass);
										}
									} catch (Throwable t) {
										IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
										exceptions.put(line, e);
									}
								}
							} // end of while read lines
						} finally {
							reader.close();
						}
					} catch (Throwable t) {
						//log.error("Exception when load extension class(interface: " + type + ", class file: " + url + ") in " + url, t);
					}
				} // end of while urls
			}
		} catch (Throwable t) {
			//log.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t);
		}
	}

	private static ClassLoader findClassLoader() {
		return ExtensionLoader.class.getClassLoader();
	}

	@Override
	public String toString() {
		return this.getClass().getName() + "[" + type.getName() + "]";
	}

}